package com.wang.tool.okhttp;

import com.alibaba.fastjson.JSON;
import com.wang.tool.common.Container;
import com.wang.tool.common.SimpleException;
import com.wang.tool.monitor.InvokeTemplate;
import com.wang.tool.monitor.core.MonitorApi;
import com.wang.tool.monitor.core.MonitorInfo;
import com.wang.tool.monitor.template.AbsMonitorInvoker;
import com.wang.tool.monitor.translator.GlobalErrorTranslator;
import com.wang.tool.monitor.weaver.BaseMonitorWeaver;
import com.wang.tool.util.CommonUtil;
import com.wang.tool.util.SimpleLogUtil;
import okhttp3.*;
import okio.BufferedSource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * okhttp调用模板
 * tips:响应体只能读一次,以下函数会导致资源关闭.一次性流
 *
 * @author wangshaopeng
 * @date 2020/8/28
 * @see
 */
public class HttpTemplate {


    /**
     * 默认客户端
     */
    private static OkHttpClient client = autoBuilder().build();


    /**
     * get文本
     *
     * @param url 地址
     * @return
     */
    public static String get(String url) {
        return get(url, null, null, null);
    }


    /**
     * get文本
     *
     * @param url     地址
     * @param timeout 超时
     * @return
     */
    public static String get(String url, Integer timeout) {
        return get(url, timeout, null, null);
    }

    /**
     * get请求
     * 参数和地址不用编码
     *
     * @param url    地址
     * @param params 参数
     * @return
     */
    public static String getWithParams(String url, Map<String, ?> params) {
        return get(url, null, null, params);
    }

    /**
     * get请求
     * 参数和地址不用编码
     *
     * @param url    地址
     * @param params 参数
     * @return
     */
    public static String getWithParams(String url, Integer timeout, Map<String, ?> params) {
        return get(url, timeout, null, params);
    }


    /**
     * get请求
     * 参数和地址不用编码
     *
     * @param url     地址
     * @param headers 请求头
     * @return
     */
    public static String getWithHeaders(String url, Map<String, String> headers) {
        return get(url, null, headers, null);
    }

    /**
     * get请求
     * 参数和地址不用编码
     *
     * @param url     地址
     * @param headers 请求头
     * @return
     */
    public static String getWithHeaders(String url, Integer timeout, Map<String, String> headers) {
        return get(url, timeout, headers, null);
    }

    /**
     * get请求
     * 参数和地址不用编码
     *
     * @param url     地址
     * @param headers 请求头
     * @param params  参数
     * @return
     */
    public static String get(String url, Integer timeout, Map<String, String> headers, Map<String, ?> params) {
        HttpUrl parse = HttpUrl.parse(url);
        if (parse == null) {
            throw new IllegalArgumentException("url 不合法!url:" + url);
        }
        return InvokeTemplate.monitor("okhttp_template", parse.scheme()+"_get",parse.host()+":"+parse.port()+parse.uri().getPath(), new AbsMonitorInvoker<String>(headers, params) {

            private Integer code;

            @Override
            public void weaveOnBefore(MonitorInfo<String> monitorInfo) throws Throwable {
                monitorInfo.addDigest("timeout", timeout == null ? client.callTimeoutMillis() : timeout);
            }

            @Override
            public String internalCall() throws Throwable {

                HttpUrl.Builder url = parse.newBuilder();
                //参数接入
                if (params != null) {
                    params.forEach((k, v) -> {
                        if (k == null) {
                            return;
                        }
                        url.addQueryParameter(k, v == null ? "" : v.toString());
                    });
                }
                //构建
                Request.Builder builder = new Request.Builder()
                        .url(url.build().toString());
                Request request = fillHeaders(builder, headers).build();
                //调用
                OkHttpClient useClient = timeout == null ? client : client.newBuilder()
                        .connectTimeout(timeout / 3, TimeUnit.MILLISECONDS)
                        .writeTimeout(timeout / 3, TimeUnit.MILLISECONDS)
                        .readTimeout(timeout, TimeUnit.MILLISECONDS)
                        .callTimeout(timeout, TimeUnit.MILLISECONDS)
                        .build();
                Response response = null;
                try {
                    response = useClient.newCall(request).execute();
                    code = response.code();
                    return transformResponse(response);
                } finally {
                    if (response != null) {
                        response.close();
                    }
                }
            }

            @Override
            protected void internalAfter(MonitorInfo<String> monitorInfo) throws Throwable {
                monitorInfo.addDigest("code", code);

            }
        });
    }


    /**
     * post请求
     * 参数和地址不用编码
     *
     * @param url    地址
     * @param params 参数
     * @return
     */
    public static String paramPost(String url, Map<String, ?> params) {
        return paramPost(url, null, null, params);
    }


    /**
     * post请求
     * 参数和地址不用编码
     *
     * @param url    地址
     * @param params 参数
     * @return
     */
    public static String paramPost(String url, Integer timeout, Map<String, ?> params) {
        return paramPost(url, timeout, null, params);
    }


    /**
     * post请求
     * 参数和地址不用编码
     *
     * @param url     地址
     * @param headers 请求头
     * @param params  参数
     * @return
     */
    public static String paramPost(String url, Integer timeout, Map<String, String> headers, Map<String, ?> params) {
        HttpUrl parse = HttpUrl.parse(url);
        if (parse == null) {
            throw new IllegalArgumentException("url 不合法!url:" + url);
        }
        return InvokeTemplate.monitor("okhttp_template", parse.scheme()+"_paramPost",parse.host()+":"+parse.port()+parse.uri().getPath(), new AbsMonitorInvoker<String>(headers, params) {

            private Integer code;

            @Override
            public void weaveOnBefore(MonitorInfo<String> monitorInfo) throws Throwable {
                monitorInfo.addDigest("timeout", timeout == null ? client.callTimeoutMillis() : timeout);
            }

            @Override
            public String internalCall() throws Throwable {
                //构建
                Request.Builder builder = new Request.Builder()
                        .url(parse.newBuilder().build().toString());

                FormBody.Builder formBodyBuilder = new FormBody.Builder();
                if (params != null) {
                    params.forEach((k, v) -> {
                        if (k == null) {
                            return;
                        }
                        formBodyBuilder.add(k, v == null ? "" : v.toString());
                    });
                }

                builder.post(formBodyBuilder.build());

                Request request = fillHeaders(builder, headers).build();

                OkHttpClient useClient = timeout == null ? client : client.newBuilder()
                        .connectTimeout(timeout / 3, TimeUnit.MILLISECONDS)
                        .writeTimeout(timeout / 3, TimeUnit.MILLISECONDS)
                        .readTimeout(timeout, TimeUnit.MILLISECONDS)
                        .callTimeout(timeout, TimeUnit.MILLISECONDS)
                        .build();
                Response response = null;
                try {
                    response = useClient.newCall(request).execute();
                    code = response.code();
                    return transformResponse(response);
                } finally {
                    if (response != null) {
                        response.close();
                    }
                }
            }

            @Override
            protected void internalAfter(MonitorInfo<String> monitorInfo) throws Throwable {
                monitorInfo.addDigest("code", code);
            }

        });

    }


    /**
     * post请求
     * 请求地址不用编码
     *
     * @param url  地址
     * @param body 请求体内容
     * @return
     */
    public static String jsonPost(String url, Object body) {
        return jsonPost(url, null, null, body);
    }

    /**
     * post请求
     * 请求地址不用编码
     *
     * @param uri  地址
     * @param body 请求体内容
     * @return
     */
    public static String jsonPost(String uri, Integer timeout, Object body) {
        return jsonPost(uri, timeout, null, body);
    }


    /**
     * post请求
     * 请求地址不用编码
     *
     * @param url     地址
     * @param headers 请求头
     * @param body    请求体内容
     * @return
     */
    public static String jsonPost(String url, Integer timeout, Map<String, String> headers, Object body) {
        HttpUrl parse = HttpUrl.parse(url);
        if (parse == null) {
            throw new IllegalArgumentException("url 不合法!url:" + url);
        }
        return InvokeTemplate.monitor("okhttp_template", parse.scheme()+"_jsonPost",parse.host()+":"+parse.port()+parse.uri().getPath(), new AbsMonitorInvoker<String>(headers, body) {

            private Integer code;

            @Override
            public void weaveOnBefore(MonitorInfo<String> monitorInfo) throws Throwable {
                monitorInfo.addDigest("timeout", timeout == null ? client.callTimeoutMillis() : timeout);
            }


            @Override
            public String internalCall() throws Throwable {
                HttpUrl parse = HttpUrl.parse(url);
                if (parse == null) {
                    throw new IllegalArgumentException("url 不合法!url:" + url);
                }
                //构建
                Request.Builder builder = new Request.Builder()
                        .url(parse.newBuilder().build().toString());
                //不会有哪个傻吊会把编码换成别的吧!
                RequestBody reqbody = RequestBody.create(body == null ? "{}" : body instanceof String ? (String) body : JSON.toJSONString(body), MediaType.parse("application/json; charset=utf-8"));
                builder.post(reqbody);
                Request request = fillHeaders(builder, headers).build();
                OkHttpClient useClient = timeout == null ? client : client.newBuilder()
                        .connectTimeout(timeout / 3, TimeUnit.MILLISECONDS)
                        .writeTimeout(timeout / 3, TimeUnit.MILLISECONDS)
                        .readTimeout(timeout, TimeUnit.MILLISECONDS)
                        .callTimeout(timeout, TimeUnit.MILLISECONDS)
                        .build();
                Response response = null;
                try {
                    response = useClient.newCall(request).execute();
                    code = response.code();
                    return transformResponse(response);
                } finally {
                    if (response != null) {
                        response.close();
                    }
                }
            }

            @Override
            protected void internalAfter(MonitorInfo<String> monitorInfo) throws Throwable {
                monitorInfo.addDigest("code", code);

            }
        });
    }


    /**
     * 异步sse模式调用
     *
     * @param url
     * @param timeout 超时ms 可为空 一般sse耗时较久建议手动指定
     * @param handler 观察者
     */
    public static <T> CompletableFuture<T> sse(String url, Integer timeout, SseHandler<T> handler) {
        return async(url, timeout, new HttpHandler<T>() {
            @Override
            public void fillRequest(Request.Builder builder) {
//                默认添加流式标准请求头
                builder.addHeader("Accept", "text/event-stream");
                builder.addHeader("X-Accel-Buffering", "no");
                //要求响应编码
                builder.addHeader("Accept-Charset", "UTF-8");
                handler.fillRequest(builder);
            }

            @Override
            public T read(Response response) throws IOException {
//                使用utf-8读取
                try (BufferedSource source = response.body().source()) {
                    while (!source.exhausted()) {
                        String line = source.readUtf8Line();
                        handler.onReadLine(line);
                    }
                    return handler.onFinished();
                }
            }

            @Override
            public T onError(Throwable error) throws Throwable {
                return handler.onError(error);
            }
        });
    }


    /**
     * sse的生成器模式
     * @param url 链接
     * @param timeout 超时时间
     * @param generator
     * @return 迭代器
     * @param <T>
     */
    public static <T> Iterator<T> sse(String url, Integer timeout, SseGenerator<T> generator) {
        SimpleGenerator<T> container = new SimpleGenerator<>();
        if (timeout != null) {
            container.setTimeout(timeout);
        }
        async(url, timeout, new HttpHandler<T>() {
            @Override
            public void fillRequest(Request.Builder builder) {
//                默认添加流式标准请求头
                builder.addHeader("Accept", "text/event-stream");
                builder.addHeader("X-Accel-Buffering", "no");
                //要求响应编码
                builder.addHeader("Accept-Charset", "UTF-8");
                generator.fillRequest(builder);
            }

            @Override
            public T read(Response response) throws IOException {
//                使用utf-8读取
                T t = null;
                try (BufferedSource source = response.body().source()) {
                    while (!source.exhausted()) {
                        String line = source.readUtf8Line();
                        t = generator.onReadLine(line);
                        if (t != null) {
                            container.push(t);
                        }
                    }
                    t = generator.onFinished();
                    if (t != null) {
                        container.push(t);
                    }
                    container.markEnd();
                    return t;
                }
            }

            @Override
            public T onError(Throwable error) {
                try {
                    T t = generator.onError(error);
                    if(t!=null){
                        container.push(t);
                    }
                    container.markEnd();
                    return t;
                } catch (Throwable e) {
                    container.markError(e);
                    return null;
                }
            }
        });
        return container.iterator();
    }


    /**
     * 异步调用http
     * 可用作文件下载等高耗时动作
     *
     * @param url     链接 非空
     * @param timeout 可为空  文件下载一般耗时较久，建议手动指定
     * @param handler 非空
     * @param <T>
     */
    public static <T> CompletableFuture<T> async(String url, Integer timeout, HttpHandler<T> handler) {
        HttpUrl parse = HttpUrl.parse(url);
        if (parse == null) {
            throw new IllegalArgumentException("url 不合法!url:" + url);
        }
        CompletableFuture<T> future = new CompletableFuture<>();
        Container<Integer> codeContainer = new Container<>();
        MonitorInfo<T> monitorInfo = MonitorApi.stepInMonitorAsync("okhttp_template",parse.scheme()+"_async",parse.host()+":"+parse.port()+parse.uri().getPath(), new BaseMonitorWeaver<T>() {
            @Override
            public void weaveOnBefore(MonitorInfo<T> monitorInfo) throws Throwable {
                monitorInfo.addDigest("timeout", timeout == null ? client.callTimeoutMillis() : timeout);
            }

            @Override
            protected void internalAfter(MonitorInfo<T> monitorInfo) throws Throwable {
                monitorInfo.addDigest("code", codeContainer.get());
            }
        }, null, System.currentTimeMillis());

        try {
            //构建
            Request.Builder builder = new Request.Builder()
                    .url(parse.newBuilder().build().toString());

            handler.fillRequest(builder);

            OkHttpClient useClient = timeout == null ? client : client.newBuilder()
                    .connectTimeout(timeout / 3, TimeUnit.MILLISECONDS)
                    .writeTimeout(timeout / 3, TimeUnit.MILLISECONDS)
                    .readTimeout(timeout, TimeUnit.MILLISECONDS)
                    .callTimeout(timeout, TimeUnit.MILLISECONDS)
                    .build();
            // 发起异步请求
            useClient.newCall(builder.build()).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    MonitorApi.stepOutMonitorAsync(monitorInfo, null, GlobalErrorTranslator.translate(e), System.currentTimeMillis());
                    try {
                        future.complete(handler.onError(e));
                    } catch (Throwable ex) {
                        future.completeExceptionally(ex);
                    }
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    try {
                        codeContainer.set(response.code());
                        if (!response.isSuccessful()) {
                            SimpleException e = new SimpleException(SimpleException.HTTP_ERROR, "async error:{0}", codeContainer.get());
                            MonitorApi.stepOutMonitorAsync(monitorInfo, null, GlobalErrorTranslator.translate(e), System.currentTimeMillis());
                            try {
                                future.complete(handler.onError(e));
                            } catch (Throwable ex) {
                                future.completeExceptionally(ex);
                            }
                            return;
                        }
                        ResponseBody body = response.body();
                        if (body == null) {
                            MonitorApi.stepOutMonitorAsync(monitorInfo, null, null, System.currentTimeMillis());
                            future.complete(null);
                            return;
                        }
                        try {
                            T result = handler.read(response);
                            MonitorApi.stepOutMonitorAsync(monitorInfo, result, null, System.currentTimeMillis());
                            future.complete(result);
                        } catch (Throwable e) {
                            MonitorApi.stepOutMonitorAsync(monitorInfo, null, GlobalErrorTranslator.translate(e), System.currentTimeMillis());
                            try {
                                future.complete(handler.onError(e));
                            } catch (Throwable ex) {
                                future.completeExceptionally(ex);
                            }
                        }
                    } finally {
                        //回收
                        response.close();
                    }
                }
            });
        } catch (Throwable e) {
            MonitorApi.stepOutMonitorAsync(monitorInfo, null, GlobalErrorTranslator.translate(e), System.currentTimeMillis());
            try {
                future.complete(handler.onError(e));
            } catch (Throwable ex) {
                future.completeExceptionally(ex);
            }
        }
        return future;
    }


    /**
     * 填充请求头
     *
     * @param builder
     * @param headers
     */
    private static Request.Builder fillHeaders(Request.Builder builder, Map<String, String> headers) {
        if (CommonUtil.isNotEmpty(headers)) {
            headers.forEach((k, v) -> {
                if (k == null) {
                    return;
                }
                builder.addHeader(k, v);
            });
        }
        return builder;
    }


    private static String transformResponse(Response response) throws IOException {
        if (!response.isSuccessful()) {
            throw new SimpleException(SimpleException.HTTP_ERROR, response.message());
        }
        ResponseBody body = response.body();
        if (body == null) {
            return null;
        }
        InputStream inputStream = body.byteStream();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }

        String contentType = response.header("Content-Type");
//        String charset = "UTF-8";
//        if (contentType != null) {
//            MediaType mediaType = MediaType.parse(contentType);
//            if (mediaType != null) {
//                charset = String.valueOf(mediaType.charset(StandardCharsets.UTF_8));
//            }
//        }
        Charset charset = StandardCharsets.UTF_8;
        if (contentType != null) {
            MediaType mediaType = MediaType.parse(contentType);
            if (mediaType != null && mediaType.charset() != null) {
                charset = mediaType.charset();
            }
        }
        return outputStream.toString(charset.name());
    }


    /**
     * ok客户端 * okhttp客户端 * OkHttp 默认的线程池配置如下：
     * 核心线程数：0
     * 最大线程数：Iteger.MAX_VALUE，其实就是不限制 * 空闲线程保活时间：60s
     * 任务队列：SynchronousQueue，为有界队列，且队列大小为 0，不存 Runnable，只是用来传递任务(存活时间内获取任务)。
     * 所以不建议重复创建OkHttpClient,而是期望共享线程池
     */
    public static OkHttpClient.Builder autoBuilder() {
        return new OkHttpClient
                .Builder()
                .retryOnConnectionFailure(false)
//                超时时间建议全部设置，不然会有默认值，如果想要超长连接的话会有坑
                .connectTimeout(7 * 1000, TimeUnit.MILLISECONDS)
                .writeTimeout(7 * 1000, TimeUnit.MILLISECONDS)
                .readTimeout(20 * 1000, TimeUnit.MILLISECONDS)
                .callTimeout(20 * 1000, TimeUnit.MILLISECONDS)
                .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
                .addInterceptor(new OkHttpTraceIdInterceptor())
                ;
    }


    public static OkHttpClient getClient() {
        return client;
    }

    public static void setClient(OkHttpClient client) {
        if (client == null) {
            throw new IllegalArgumentException("client can not be null");
        }
        HttpTemplate.client = client;
        SimpleLogUtil.info("[changeHttpTemplateClient], client:{0}", client);
    }

}
